package org.dreamwork.modbus.tcp;

import org.dreamwork.modbus.protocol.PDU;
import org.dreamwork.modbus.tcp.protocol.MBAP;
import org.dreamwork.modbus.tcp.protocol.TcpADU;
import org.dreamwork.util.Tools;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import static org.dreamwork.modbus.Predefined.Function.*;
import static org.dreamwork.modbus.Predefined.Function.Write_Multiple_Registers;

public class Helper {
    public static byte[] readRawPacket (InputStream in) throws IOException {
        byte[] buff = new byte[256];
        int length  = in.read (buff);
        if (length != -1) {
            return Tools.slice (buff, 0, length);
//            return buff;
        }
        throw new EOFException ();
/*
        int pos = 0, length = 0, ch;
        while (true) {
            if ((ch = in.read ()) != -1) {
                buff[pos ++] = (byte) ch;
                if (pos == 6) { // modbus tcp 的 mbap 结构中，第 4,5 字节为报文长度
                    length = (buff[4] & 0xff) << 8 | ch;
                } else if (pos == length + 6) {
                    byte[] data = new byte[pos];
                    System.arraycopy (buff, 0, data, 0, pos);
                    return data;
                }
            }
        }
*/
    }

    public static TcpADU read (InputStream in) throws IOException {
        byte[] buff = new byte[256];
        int pos = 0, length = 0;
        while (true) {
            int ch = in.read ();
            if (ch != -1) {
                buff [pos ++] = (byte) ch;
                if (pos == 6) { // modbus tcp 的 mbap 结构中，第 4,5 字节为报文长度
                    length = (buff[4] & 0xff) << 8 | ch;
                } else if (pos == length + 6) { // 完整报文读取完成
                    return parse (buff);
/*
                    TcpADU adu = parse (buff);
                    PDU pdu;
                    try {
                        pdu = perform (listener, adu.pdu);
                    } catch (ModbusException me) {
                        pdu = new PDU ();
                        pdu.function = adu.pdu.function | 0x80; // 有错误发送，MSB置1
                        pdu.error    = me.code;
                    }

                    try {
                        TcpADU response = new TcpADU ();
                        response.header = adu.header;
                        response.pdu    = pdu;
                        out.write (response.toByteArray ());
                        out.flush ();
                    } finally {
                        // 所有指针归零
                        pos = 0;
                        length = 0;
                    }
*/
                }
            }
        }
    }

    public static TcpADU parse (byte[] buff) {
        int pos = 0;
        TcpADU adu = new TcpADU ();
        MBAP header = new MBAP ();
        header.transaction = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
        header.protocol    = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
        header.length      = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
        header.unit        =   buff[pos ++] & 0xff;
        adu.header = header;
        PDU pdu = new PDU ();
        pdu.function = buff[pos ++] & 0xff;
        if (pdu.function == 0x08 || pdu.function == 0x2b) {
            pdu.subCode = buff[pos ++] & 0xff;
        }
        switch (pdu.function) {
            case Read_Coils:
            case Read_Discrete_Inputs:
            case Read_Holding_Registers:
            case Read_Input_Registers:
                pdu.address = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
                pdu.count   = ((buff[pos ++] & 0xff) << 8) | (buff[pos] & 0xff);
                break;
            case Write_Single_Coil:
            case Write_Single_Register:
                pdu.address = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
                pdu.value   = ((buff[pos ++] & 0xff) << 8) | (buff[pos] & 0xff);
                break;
            case Write_Multiple_Coils:
            case Write_Multiple_Registers:
                pdu.address = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
                pdu.count   = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
                int length  =   buff[pos ++] & 0xff;
                pdu.data    = new byte[length];
                System.arraycopy (buff, pos, pdu.data, 0, length);
                break;
            default:
                // 其他公共功能码暂时不做识别
                pdu.data = new byte[header.length - 1 - 1]; // 头部的length本身包含了unit(1)，然后再扣除已经读过的 function(1)
                System.arraycopy (buff, pos, pdu.data, 0, pdu.data.length);
                break;
        }

        adu.pdu = pdu;
        return adu;
    }

    /**
     * 将布尔数组转化为字节数组，转换规则为 {@link #byte2bool(byte[], int, int, int)} 的逆向规则。
     * @param bits 布尔数组
     * @return 转换后的字节数组
     */
    public static byte[] bool2byte (boolean[] bits) {
        int length = bits.length / 8;
        if (bits.length % 8 > 0) length ++;
        byte[] data = new byte[length];
        for (int i = 0; i < bits.length; i ++) {
            if (bits[i]) {
                int mask = i % 8;
                int column = i / 8;
                data[column] |= 1 << mask;
            }
        }
        return data;
    }

    /**
     * 将字节数组转成布尔数组.
     * <p>根据 modbus 线圈取值规则，字节数组中首字节的最低位为结果数组的第0个元素，次字节最低位为结果数组中第8个元素，以此类推。</p>
     * 例如：<pre><code>data: [0x12, 0x34]</code></pre>
     * 转换为二进制：<pre><code>[00010010, 00110100]</code></pre>
     * 则转换结果为<pre><code>[
     *     f, t, f, f, t, f, f, f,     // 首字节各位逆序
     *     f, f, t, f, t, t, f, f      // 次字节
     * ]</code></pre>
     * @param data   字节数组
     * @param count  转换结果的数组长度
     * @return 转换后的布尔数组
     */
    public static boolean[] byte2bool (byte[] data, int count) {
        return byte2bool (data, 0, data.length, count);
    }

    /**
     * 将字节数组转成布尔数组.
     * <p>根据 modbus 线圈取值规则，字节数组中首字节的最低位为结果数组的第0个元素，次字节最低位为结果数组中第8个元素，以此类推。</p>
     * 例如：<pre><code>data: [0x12, 0x34]</code></pre>
     * 转换为二进制：<pre><code>[00010010, 00110100]</code></pre>
     * 则转换结果为<pre><code>[
     *     f, t, f, f, t, f, f, f,     // 首字节各位逆序
     *     f, f, t, f, t, t, f, f      // 次字节
     * ]</code></pre>
     * 详细规则请参见 modbus rfc
     * @param data   字节数组
     * @param offset 偏移量
     * @param length 长度
     * @param count  转换结果的数组长度
     * @return 转换后的布尔数组
     */
    public static boolean[] byte2bool (byte[] data, int offset, int length, int count) {
        boolean[] bits = new boolean[count];    // 每个字节对应8个线圈
        int pos = 0;
        for (int i = 0; i < length; i ++) {
            byte b = data[i + offset];
            for (int j = 0; j < 8 && pos < count; j ++) {
                bits[pos ++] = (b & (1 << j)) != 0;
            }
        }
        return bits;
    }

    public static byte[] int2byte (int[] values) {
        int length = values.length * 2;             // modbus 中每个寄存器值占2个字节
        byte[] data = new byte[length];
        int pos = 0;
        for (int n : values) {
            data[pos ++] = (byte) (n >> 8);
            data[pos ++] = (byte) (n & 0xff);
        }
        return data;
    }

    public static int[] byte2int (byte[] buff, int offset, int length) {
        if ((length >> 1) << 1 != length) {
            throw new IllegalArgumentException ("invalid length");
        }
        int[] data = new int[length >> 1];
        int pos = offset;
        for (int i = 0; i < data.length; i ++) {
            data[i] = ((buff[pos ++] & 0xff) << 8) | (buff[pos ++] & 0xff);
        }
        return data;
    }
}
